﻿/// -----------------------------------------------------------------------
/// (c) 2009 Microsoft Corporation -- All rights reserved
/// This code is licensed under the MS-PL
/// http://www.opensource.org/licenses/ms-pl.html
/// Courtesy of the Open Source Techology Center: http://port25.technet.com
/// -----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO;
using System.Xml.XPath;
using System.Xml;
using gsToolkit;
using Microsoft.VisualStudio.VCProjectEngine;

namespace mkProject
{
    class MakeProject
    {

//-------|---------|---------|---------|---------|---------|---------|---------|

        static string help = @"
Usage:
-------

mkProject [options] <path\to\logfile.xml>

    options:
    --------
    --help                      this help
    --nologo                    don't display the logo
    --load-config=<configfile>  loads options from configuration file
                                List the options, one per line, without the
                                prefix '--'.

    --project-root              the root directory of all activity to monitor.
                                this assumes everything not in here is system
                                or compiler related, and will be filtered out
                                for purposes of identifying targets/objs

    --create-solution=<file>    creates a solution file that contains all of
                                projects updated and/or created

    --rename=[<orig>]<chgd>     renames the project from <orig> to <chgd>
    --ignore=<project>          ignores this project when generating files
    --redirect=<file1>,<file2>  during log analysis, changes <file1> to <file2>

    --project-templates=<path> *sets directory to find the template projects
    --output-directory=<path>  *sets directory to generate the projects into
    --prefix=<prfx>            *adds the prefix <prfx> to the project name
    --ignore-library=<file>    *ignores a given file as a linker input.
    --add-library=<file>       *adds an additional library to the linker input.
    --add-dependency=<proj>    *adds a sibling project as an input dependency
                                can also be a project GUID in the template SLN.
    --ignore-source=<file.obj> *ignores the source file, drops from the link
    --skip-source=<file.obj>   *doesn't add the source file to the project,but
                                still links it in the target.

    --ignore-command=<rx>       ignores commands in the log matching regex
    --post-build-command=<rx>  *assigns command as post-build step
    --pre-link-command=<rx>    *assigns command as pre-link step
    --pre-build-command=<rx>   *assigns command as pre-build step

    --dry-run                   does not save the project files at the end.
    --recreate-projects         recreates the vcproject files from scratch.
    --reset-libraries           resets the library definitions
    --reset-include-paths       resets the include paths
    --reset-library-paths       resets the library paths
    --reset-defines             resets the preprocessor definitions
    --clean-files               removes all the existing files from the project
    --preserve-propertysheets   preserves inherited property sheets during
                                round-tripping.

    --add-propertysheets=<sht> *adds a InheritedPropertySheet to the project
                                with substitutions:
                                    $MACHINETYPE    -- x86 or x64
                                    $CONFIGURATION  -- Release or Debug
                                    $EXTENSION      -- dll, exe or lib

    Notes:
          Options marked with an * may specify a [project] before the
          argument to narrow the application of the option to only that
          project. [project] is evaulated as a regular expression.
                (ie: --pre-build-command=[.*openssl]ml.exe )
";

        string moreNotes = @"
Ignoring commands per project, means that it is only ignored for that project.

pre/post-build/link-commands when used per project automatically means the same commands are ignored for other projects.

";

        const string DEFAULT_projectTemplateFilesPath = @"C:\root\projects\work\TemplateSolution\";
        /*const*/
        string DEFAULT_outputFilesPath = Path.GetFullPath(@".\").ToLower();

        // these are all 1 value per project maximum
        public Dictionary<string, string> projectTemplateFilesPath;
        public Dictionary<string, string> outputFilesPath;
        public Dictionary<string, string> projectNameAliases;
        public Dictionary<string, string> projectNamePrefixes;
        public Dictionary<string, string> ignoreProjects;

        // these are all multi-value per project
        public Dictionary<string, List<string>> skipSources;       // skip the search for the file, but still link
        public Dictionary<string, List<string>> ignoreSources;     // skip the search for the file, don't worry about linking

        public Dictionary<string, List<string>> post_build_commands; // command patterns to watch for
        public Dictionary<string, List<string>> pre_link_commands;
        public Dictionary<string, List<string>> pre_build_commands;

        public Dictionary<string, List<string>> add_post_build_commands;
        public Dictionary<string, List<string>> add_pre_link_commands;
        public Dictionary<string, List<string>> add_pre_build_commands;

        public Dictionary<string, List<string>> ignore_commands;

        public Dictionary<string, List<string>> propertySheets;
        public Dictionary<string, List<string>> ignoreLibraries;
        public Dictionary<string, List<string>> addLibraries;
        public Dictionary<string, List<string>> addDependencies;

        public Dictionary<string, string> redirects;

        public string ProjectRoot = Path.GetFullPath(Environment.CurrentDirectory).ToLower();
        public string logFilePath;
        public bool recreate = false;
        public bool clean_libs = false;
        public bool clean_libPath = false;
        public bool clean_includes = false;
        public bool clean_defines = false;
        public bool clean_files = false;
        public bool dryrun = false;
        public bool preservePropertySheets = false;
        public string SolutionFile;


        // interface for handling project files.
        public VCProjectEngineObjectClass VCProjectEngine = new VCProjectEngineObjectClass();


        public XmlDocument TraceXmlDocument = new XmlDocument();

        public XmlNodeList targetProcs;

        public List<Target> OriginalTargets = new List<Target>();

        private bool failTripped;

        public static MakeProject App;

        [STAThread]
        static int Main(string[] args)
        {
            App = new MakeProject();
            int result = App.main(args);
            return result;
        }

        int main(string[] args)
        {

            #region Options and Parameters
            foreach (var arg in args.Switches().Keys)
            {
                var parameters = args.Switches()[arg];

                switch (arg)
                {
                    /* directory-based options  */
                    case "project-templates":
                        if (projectTemplateFilesPath != null)
                            break; // skip this if it's already processed by an earlier parameter.

                        projectTemplateFilesPath = TransformParametersToPerProjectDirectory(parameters, DEFAULT_projectTemplateFilesPath);
                        if (projectTemplateFilesPath == null)
                            return Fail("option --project-templates has an invalid value");
                        break;

                    case "output-directory":
                        if (outputFilesPath != null)
                            break; // skip this if it's already processed by an earlier parameter.

                        outputFilesPath = TransformParametersToPerProjectDirectory(parameters, DEFAULT_outputFilesPath);
                        if (outputFilesPath == null)
                            return Fail("option --output-directory has an invalid value");
                        break;

                    /* single-value per project options  */
                    case "rename":
                        projectNameAliases = TransformParametersToPerProjectDictionaries(parameters);
                        break;

                    case "prefix":
                        projectNamePrefixes = TransformParametersToPerProjectDictionaries(parameters);
                        break;

                    case "ignore":
                        ignoreProjects = TransformParametersToPerProjectDictionaries(parameters);
                        break;


                    /* multi-value per project options */
                    case "ignore-command":
                        ignore_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "post-build-command":
                        post_build_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "pre-link-command":
                        pre_link_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "pre-build-command":
                        pre_build_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-post-build-command":
                        add_post_build_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-pre-link-command":
                        add_pre_link_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-pre-build-command":
                        add_pre_build_commands = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "dont-fail":
                        ignoreSources = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "skip-source":
                        skipSources = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-propertysheets":
                        propertySheets = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "ignore-library":
                        ignoreLibraries = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-library":
                        addLibraries = TransformParametersToPerProjectLists(parameters);
                        break;

                    case "add-dependency":
                        addDependencies = TransformParametersToPerProjectLists(parameters);
                        break;

                    /* global switches */
                    case "redirect":
                        if (redirects == null)
                        {
                            redirects = new Dictionary<string, string>();
                            foreach (string p in parameters)
                            {
                                string[] p2 = p.Split(',');
                                if (p2.Length != 2)
                                    return Fail("Invalid Redirect option [{0}]", p);
                                redirects.Add(p2[0].ToLower(), p2[1]);
                            }
                        }
                        break;

                    case "dry-run":
                        dryrun = true;
                        break;

                    case "preserve-propertysheets":
                        preservePropertySheets = true;
                        break;

                    case "create-solution":
                        SolutionFile = Path.GetFullPath(parameters.Last()).ToLower();
                        break;

                    case "project-root":
                        ProjectRoot = Path.GetFullPath(parameters.Last()).ToLower();
                        if (!Directory.Exists(ProjectRoot))
                        {
                            return Fail("Project root directory does not exist [{0}]", ProjectRoot);
                        }
                        break;
                    case "recreate-projects":
                        recreate = true;
                        break;

                    case "reset-libraries":
                        clean_libs = true;
                        break;

                    case "reset-include-paths":
                        clean_includes = true;
                        break;

                    case "reset-library-paths":
                        clean_libPath = true;
                        break;

                    case "reset-defines":
                        clean_defines = true;
                        break;

                    case "clean-files":
                        clean_files = true;
                        break;

                    case "load-config":
                        // all ready done, but don't get too picky.
                        break;

                    case "nologo":
                        this.Assembly().SetLogo("");
                        break;

                    case "help":
                        Logo();
                        return Help();

                    default:
                        Logo();
                        return Fail("Unknown option [{0}]", arg);
                }
            }

            if (args.Parameters().Count == 0)
            {
                Logo();
                return Fail("No input log file specified \r\n\r\n    Use --help for command line help.");
            }

            // should we expand this to do wildcards?
            logFilePath = Path.GetFullPath(args.Parameters()[0]).ToLower();
            if (!File.Exists(logFilePath))
            {
                return Fail("Unable to find log file :[{0}]", logFilePath);
            }
            #endregion

            Logo();
            return ProcessLog();
        }
        public int ProcessLog()
        {

            #region Parse Thru XML Log

            StringBuilder xsb = new StringBuilder();
            string[] doc = File.ReadAllLines(logFilePath);
            for (int i = 0; i < doc.Length; i++)
                xsb.AppendLine(doc[i].StartsWith("<File ") ? doc[i].ToLower() : doc[i]);


            TraceXmlDocument.LoadXml(xsb.ToString());

            #endregion

            #region Identify what was built
            // identify all targets
            targetProcs = TraceXmlDocument.XPath("//file[@write = 'true' and ( contains( . , '.exe') or contains( . , '.dll') or contains( . , '.lib') )  ]/ancestor::Process[@exe != 'rc']");
            foreach (XmlNode n in targetProcs)
            {
                // for each target,
                try
                {
                    if (n.SelectNodes("Child[@exe='link']").Count == 0)
                        OriginalTargets.Add(new Target(n));
                }
                catch (Exception e)
                {
                    // Console.WriteLine("Failed on one target process: \r\n{0}",n.OuterXml);
                    //
                }
            }
            #endregion
            if (failTripped)
                return 1;

            foreach (Target target in OriginalTargets)
            {
                TargetCommandLine targetCommand = target.TargetCommand;

                foreach (VCConfiguration conf in (System.Collections.IEnumerable)target.VCProject.Configurations)
                {
                    foreach (object Tool in (System.Collections.IEnumerable)conf.Tools)
                    {
                        #region LinkerTool
                        var linkTool = Tool as VCLinkerTool;
                        if (linkTool != null)
                        {

                            linkTool.AdditionalLibraryDirectories = UpdateDelimitedList(linkTool.AdditionalLibraryDirectories, targetCommand.libPaths, ';');
                            linkTool.AdditionalDependencies = UpdateDelimitedList(clean_libs ? "" : linkTool.AdditionalDependencies, targetCommand.libFiles, ' ');
                            // linkTool.AdditionalDependencies =  UpdateDelimitedList(linkTool.AdditionalDependencies, targetCommand.additionalLinkerInputs, ' ');
                            linkTool.ModuleDefinitionFile = targetCommand.defFile;
                        }
                        #endregion

                        #region LibTool
                        var libTool = Tool as VCLibrarianTool;
                        if (libTool != null)
                        {
                            libTool.AdditionalLibraryDirectories = UpdateDelimitedList(libTool.AdditionalLibraryDirectories, targetCommand.libPaths, ';');
                            libTool.AdditionalDependencies = UpdateDelimitedList(clean_libs ? "" : libTool.AdditionalDependencies, targetCommand.libFiles, ' ');
                            // libTool.AdditionalDependencies =  UpdateDelimitedList(libTool.AdditionalDependencies, targetCommand.additionalLinkerInputs, ' ');
                            libTool.ModuleDefinitionFile = targetCommand.defFile;
                        }
                        #endregion

                        #region CompilerTool
                        var clTool = Tool as VCCLCompilerTool;
                        if (clTool != null)
                        {
                            clTool.PreprocessorDefinitions = UpdateDelimitedList(clean_defines ? "" : clTool.PreprocessorDefinitions, target.CommonDefines, ';');
                            clTool.AdditionalIncludeDirectories = UpdateDelimitedList(clean_includes ? "" : clTool.AdditionalIncludeDirectories, target.CommonIncludes, ';');
                        }
                        #endregion

                        #region PreBuildEventTool
                        var preBuildTool = Tool as VCPreBuildEventTool;
                        if (preBuildTool != null)
                        {
                            preBuildTool.Description = target.preBuildScript.Count > 0 ? "Pre Build Steps" : null;
                            preBuildTool.CommandLine = target.preBuildScript.Count > 0 ? target.preBuildScript.Combine("\r\n") : null;
                        }
                        #endregion

                        #region PreLinkEventtool
                        var preLinkTool = Tool as VCPreLinkEventTool;
                        if (preLinkTool != null)
                        {
                            preLinkTool.Description = target.preLinkScript.Count > 0 ? "Pre Link Steps" : null;
                            preLinkTool.CommandLine = target.preLinkScript.Count > 0 ? target.preLinkScript.Combine("\r\n") : null;
                        }
                        #endregion

                        #region PostBuildEventTool
                        var postBuildTool = Tool as VCPostBuildEventTool;
                        if (postBuildTool != null)
                        {
                            postBuildTool.Description = target.postBuildScript.Count > 0 ? "Post Build Steps" : null;
                            postBuildTool.CommandLine = target.postBuildScript.Count > 0 ? target.postBuildScript.Combine("\r\n") : null;
                        }
                        #endregion
                    }
                }

                #region Add Source Files
                if (clean_files)
                {
                    while (((IVCCollection)target.VCProject.Files).Count > 0)
                        target.VCProject.RemoveFile(((IVCCollection)target.VCProject.Files).Item(1));
                    while (((IVCCollection)target.VCProject.Filters).Count > 0)
                        target.VCProject.RemoveFilter(((IVCCollection)target.VCProject.Filters).Item(1));
                }

                foreach (string headerFile in target.sHFiles)
                {
                    try
                    {
                        VCFilter filter = GetFilter(target.VCProject, headerFile, ProjectRoot.GetSubPath(headerFile));
                        VCFile newFile = filter.AddFile(headerFile) as VCFile;
                    }
                    catch
                    {
                    }
                }

                List<string> skipSrc = skipSources.Lookup(target.ProjectName);

                foreach (SourceFile sourceFile in target.sourceFiles)
                {

                    if (skipSrc.Contains(sourceFile.fileName))
                        continue;

                    try
                    {
                        VCFilter filter = GetFilter(target.VCProject, sourceFile.fileName, sourceFile.subDir);
                        VCFile newFile = filter.AddFile(sourceFile.fileName) as VCFile;

                        foreach (VCFileConfiguration conf in (IVCCollection)newFile.FileConfigurations)
                        {
                            VCCLCompilerTool vcTool = conf.Tool as VCCLCompilerTool;
                            VCResourceCompilerTool rcTool = conf.Tool as VCResourceCompilerTool;

                            if (vcTool != null && !string.IsNullOrEmpty(sourceFile.subDir))
                            {
                                vcTool.ObjectFile = "$(IntDir)\\{0}\\".format(sourceFile.subDir);
                            }

                            foreach (string include in sourceFile.includes)
                            {
                                if (!target.CommonIncludes.Contains(include))
                                {
                                    if (vcTool != null)
                                    {
                                        string includes = vcTool.AdditionalIncludeDirectories;
                                        includes = includes == null ? include : includes + ";" + include;
                                        vcTool.AdditionalIncludeDirectories = includes;
                                    }
                                    if (rcTool != null)
                                    {
                                        string includes = rcTool.AdditionalIncludeDirectories;
                                        includes = includes == null ? include : includes + ";" + include;
                                        rcTool.AdditionalIncludeDirectories = includes;
                                    }
                                }
                            }

                            foreach (string define in sourceFile.defines)
                            {
                                if (!target.CommonDefines.Contains(define))
                                {
                                    if (vcTool != null)
                                    {
                                        string defines = vcTool.PreprocessorDefinitions;
                                        defines = defines == null ? define : defines + ";" + define;
                                        vcTool.PreprocessorDefinitions = defines;
                                    }
                                    if (rcTool != null)
                                    {
                                        string defines = rcTool.PreprocessorDefinitions;
                                        defines = defines == null ? define : defines + ";" + define;
                                        rcTool.PreprocessorDefinitions = defines;
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception)
                    {
                        // it's already there I guess
                    }
                }
                #endregion


            }

            if (failTripped)
                return 1;

            // everything went ok? save 'em
            if (!dryrun)
            {

                foreach (Target t in OriginalTargets)
                {
                    t.VCProject.Save();

                    #region Add Property Sheet Lines
                    string filename = t.VCProject.ProjectFile;
                    string prj = File.ReadAllText(filename);
                    string sheetLine = propertySheets.Lookup(t.ProjectName).Combine(";");

                    foreach (VCConfiguration conf in (System.Collections.IEnumerable)t.VCProject.Configurations)
                    {
                        string cfg = "<Configuration\r\n\t\t\tName=\"{0}\"".format(conf.Name);
                        string plat = ((VCPlatform)conf.Platform).Name;
                        string cnf = conf.Name.Substring(0, conf.Name.IndexOf("|"));
                        string cfgSheet = sheetLine.Replace("$MACHINETYPE", plat.Equals("Win32") ? "x86" : plat).Replace("$CONFIGURATION", cnf).Replace("$EXTENSION", t.outputExtension.Substring(t.outputExtension.IndexOf(".") + 1));
                        prj = prj.Replace(cfg, @"{0} InheritedPropertySheets=""{1}""".format(cfg, cfgSheet));
                    }

                    File.WriteAllText(filename, prj);
                    #endregion
                }

                #region Write Solution File
                // write out solution file
                if (!string.IsNullOrEmpty(SolutionFile))
                {
                    string slnpath = Path.Combine(projectTemplateFilesPath.Lookup("solution"), "Template.sln");
                    string solution = File.ReadAllText(slnpath);
                    // solution = solution.EncodeToCString();
                    List<string> macros = new List<string>();

                    Regex rx = new Regex("#BEGIN(.*?)#END", RegexOptions.Singleline);
                    MatchCollection matches = rx.Matches(solution);
                    foreach (Match match in matches)
                    {
                        macros.Add(match.Groups[1].Value);
                    }

                    foreach (string macro in macros)
                    {
                        StringBuilder tmp = new StringBuilder();
                        foreach (Target t in OriginalTargets)
                        {
                            tmp.Append(macro
                                              .Replace("$PROJNAME", t.ProjectName)
                                              .Replace("$PROJFILE", t.VCProject.ProjectFile.Substring(ProjectRoot.Length + 1))
                                              .Replace("$PROJGUID", t.VCProject.ProjectGUID.ToUpper())
                                              .Replace("$DEPENDLIST", t.DependList)
                                );

                        }
                        solution = solution.Replace("#BEGIN{0}#END".format(macro), tmp.ToString());
                    }
                    // solution = solution.DecodeFromCString();
                    File.WriteAllText(SolutionFile, solution);
                }
                #endregion
            }

            return 0;
        }

        private string UpdateDelimitedList(string delimitedList, List<string> list, char seperator)
        {
            List<string> elements = delimitedList.SplitToList(seperator).Uniq();
            foreach (string each in list)
            {
                if (!elements.Contains(each))
                    elements.Add(each);
            }
            return elements.Combine(seperator);
        }

        private static VCFilter GetFilter(VCProject vcProject, string filename, string subDir)
        {
            string extension = filename.Substring(filename.LastIndexOf(".") + 1);

            foreach (VCFilter filter in (IVCCollection)vcProject.Filters)
            {
                List<string> tmp = new List<string>();
                tmp.AddRange(filter.Filter.Split(';'));
                if (tmp.Contains(extension))
                    return GetFilter(filter, subDir);
            }
            return null;
        }

        private static VCFilter GetFilter(VCFilter vcFilter, string subDir)
        {
            if (string.IsNullOrEmpty(subDir))
                return vcFilter;

            int pos = subDir.IndexOf('\\');
            string filterName = (pos == -1) ? subDir : subDir.Substring(0, pos);
            string subFilterName = (pos == -1) ? "" : subDir.Substring(pos + 1);
            foreach (VCFilter filter in (IVCCollection)vcFilter.Filters)
            {
                if (filter.Name == filterName)
                {
                    return string.IsNullOrEmpty(subFilterName) ? filter : GetFilter(filter, subFilterName);
                }
            }
            return string.IsNullOrEmpty(subFilterName) ? (VCFilter)vcFilter.AddFilter(filterName) : GetFilter((VCFilter)vcFilter.AddFilter(filterName), subFilterName);
        }

        public string Redirect(string originalFilename)
        {
            if (redirects != null)
            {
                string key = originalFilename.ToLower();
                if (redirects.ContainsKey(key))
                    return redirects[key];
            }
            return originalFilename;
        }

        #region TransformParameters
        private Dictionary<string, List<string>> TransformParametersToPerProjectLists(List<string> parameters)
        {
            var result = new Dictionary<string, List<string>>();
            result[ProgramExtensions.REGEX_ALL] = new List<string>();

            // GS01: doesn't support [] in regexes... if there is a problem, solve it with \] later.
            Regex rx = new Regex(@"\[(.*?)\](.*)");

            foreach (string s in parameters)
            {
                Match m = rx.Match(s);
                if (m.Success)
                {
                    if (!result.ContainsKey(m.Groups[1].Value))
                        result.Add(m.Groups[1].Value, new List<string>());
                    result[m.Groups[1].Value].Add(m.Groups[2].Value);
                }
                else
                {
                    result[ProgramExtensions.REGEX_ALL].Add(s);
                }
            }
            return result;
        }

        private Dictionary<string, string> TransformParametersToPerProjectDirectory(List<string> parameters, string DEFAULT_Path)
        {
            var result = new Dictionary<string, string>();
            result[ProgramExtensions.REGEX_ALL] = DEFAULT_Path;

            // GS01: doesn't support [] in regexes... if there is a problem, solve it with \] later.
            Regex rx = new Regex(@"\[(.*?)\](.*)");

            foreach (string s in parameters)
            {
                Match m = rx.Match(s);
                if (m.Success)
                {
                    string path = Path.GetFullPath(m.Groups[2].Value).ToLower();
                    if (!Directory.Exists(path))
                    {
                        Fail("Path [{0}] does not exist", path);
                        return null;
                    }
                    result.Add(m.Groups[1].Value, path);
                }
                else
                {
                    string path = Path.GetFullPath(s).ToLower();
                    if (!Directory.Exists(path))
                    {
                        Fail("Path [{0}] does not exist", path);
                        // didn't match a per-project setting, override the default with this
                        return null;
                    }
                    result[ProgramExtensions.REGEX_ALL] = path;
                }
            }
            return result;
        }

        private Dictionary<string, string> TransformParametersToPerProjectDictionaries(List<string> parameters)
        {
            return TransformParametersToPerProjectDictionaries(parameters, null);
        }

        private Dictionary<string, string> TransformParametersToPerProjectDictionaries(List<string> parameters, string DEFAULT_Value)
        {
            var result = new Dictionary<string, string>();
            if (DEFAULT_Value != null)
                result[ProgramExtensions.REGEX_ALL] = DEFAULT_Value;

            // GS01: doesn't support [] in regexes... if there is a problem, solve it with \] later.
            Regex rx = new Regex(@"\[(.*?)\](.*)");

            foreach (string s in parameters)
            {
                Match m = rx.Match(s);
                if (m.Success)
                {
                    result.Add(m.Groups[1].Value, m.Groups[2].Value);
                }
                else
                {
                    result[ProgramExtensions.REGEX_ALL] = s;
                }
            }
            return result;
        }
        #endregion

        #region fail/help/logo
        public int Fail(string text, params object[] par)
        {
            failTripped = true;
            using (new ConsoleColors(ConsoleColor.Red, ConsoleColor.Black))
                Console.WriteLine("Error:{0}", text.format(par));
            return 1;
        }

        int Help()
        {
            using (new ConsoleColors(ConsoleColor.White, ConsoleColor.Black))
                help.Print();
            return 0;
        }

        void Logo()
        {
            using (new ConsoleColors(ConsoleColor.Cyan, ConsoleColor.Black))
                this.Assembly().Logo().Print();
            this.Assembly().SetLogo("");
        }
        #endregion
    }

    public static class ProgramExtensions
    {
        public const string REGEX_ALL = ".*";

        /// <summary>
        /// Looks up a single value on a per-project basis.
        /// </summary>
        /// <param name="dict"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public static string Lookup(this Dictionary<string, string> dict, string value)
        {
            if (dict == null)
                return value;

            foreach (string key in dict.Keys)
            {
                if (key == REGEX_ALL)
                    continue;

                if (value.Match(key).Success)
                    return dict[key];
            }
            if (dict.Keys.Contains(REGEX_ALL))
                return dict[REGEX_ALL];

            return value;
        }

        /// <summary>
        /// Looks up a list of values for on a per project basis.
        /// </summary>
        /// <param name="dict"></param>
        /// <param name="projectName"></param>
        /// <returns></returns>
        public static List<string> Lookup(this Dictionary<string, List<string>> dict, string projectName)
        {
            List<string> result = new List<string>();
            if (dict == null)
                return result;

            foreach (string key in dict.Keys)
            {
                if (key == REGEX_ALL)
                    continue;
                if (key[0] == '!')
                {
                    if (!projectName.Match(key.Substring(1)).Success)
                        result.AddRange(dict[key]);
                }
                else
                {
                    if (projectName.Match(key).Success)
                        result.AddRange(dict[key]);
                }
            }
            if (dict.Keys.Contains(REGEX_ALL))
                result.AddRange(dict[REGEX_ALL]);

            return result;
        }

        public static List<string> LookupReverse(this Dictionary<string, List<string>> dict, string projectName)
        {
            List<string> result = new List<string>();
            if (dict == null)
                return result;

            foreach (string key in dict.Keys)
            {
                if (key == REGEX_ALL)
                    continue;

                if (!projectName.Match(key).Success)
                    result.AddRange(dict[key]);
            }
            return result;
        }
    }

}
